# vue和ext表单相互嵌套使用
# 背景
由于控制器是vue+ext双框架的项目,且存在很多老的组件是由ext实现的,在新页面的开发中如果要使用到就会很麻烦,并且使用vue重写的话成本巨大且无法保证覆盖全面,于是实现以下互相嵌套使用的方案,解决这一困扰点。
# vue表单中使用ext
之前已经实现过了单独调用ext弹窗的方案,这种比较独立,所以还是很好实现的。
另外存在的大场景就是combo类组件,表单中嵌入输入框,点击显示弹窗的那种。比如选择端口、交换机面板、各种设备的树形选择等等,都是用ext做的。
# 原理
这种做起来也简单,原理如下:
- vue提供个组件容器,并调用useExt将其转化为ext容器。
 - 实例化传入的ext组件,挂载到容器上。
 - 注册到Idux的表单系统中
 
首先调用idux的自定义表单
const { accessor, control: controlRef } = useAccessorAndControl();
useFormItemRegister(controlRef);
劫持ext的setJsonValue,在ext组件设置值时将值同步设置到idux表单中
extOptions.value.setJsonValue = function (..._args: SafeAny) {
  const [v] = _args;
  accessor.setValue(v);
  formItemValue.value = v;
  props.extComponent.prototype.setJsonValue.apply(this, _args);
};
在回显时,watch idux表单值的变化,同步设置给ext组件
watch(
  () => accessor.value,
  v => {
    if (v !== formItemValue.value) {
      containerRef.value?.setJsonValue(v);
    }
  }
);
通过以上几步即可将ext组件嵌入到vue表单中使用
其它的非表单组件也可参照该思想去实现。
# 使用
使用上遵循简单易用的原则
直接调用ExtComboToVue组件,传入ext组件的构造函数、所需配置,以及idux表单中的control,即可像使用vue组件一样轻松。
<template>
  <ExtComboToVue
    :control="props.control"
    :ext-component="SwitchTreeCombo"
    :ext-options="LogComboOptions" />
</template>
<script lang="ts" setup>
import { logTreeComboProps } from './types';
import '~/js/business/Switch/SwitchTreeCombo.js';
import { ExtComboToVue } from '@/components/ExtComboToVue';
const _ = window._;
const props = defineProps(logTreeComboProps);
const SwitchTreeCombo = B.Switch.SwitchTreeCombo;
const LogComboOptions = {
  xx: xx
};
</script>
# ext表单中使用vue
ext中使用vue相对就比较麻烦,这里参考了深信服两位同事林浩钦(数据嵌入ext表单)、余龙栋(vue组件渲染、事件)的实现。
# 原理
# vue组件渲染、事件
大致原理就是传入vue组件到一个Ext.BoxComponent构造函数中,在ext渲染时动态的渲染vue并挂载到节点上。(这种原理我们之前也实现过了,只是没有封装)
另外在ext中监听了vue的emit事件,并可对外暴露事件,达到数据交换的操作。
如果单纯的只是渲染,这个封装的函数还是能达到效果。
但是有个缺点就是没有嵌入到ext表单中,设置值、获取值都需要手动处理,十分的麻烦,所以需要接入以下方案
# 数据嵌入ext表单
这里做的操作就是在前面的基础上配置了setJsonValue,getJsonValue及validator,解决了表单值的获取、回显及校验
并且获取外层ext中配置的校验限制,传入到vue的props,在vue组件中我们需要调用给定的hooks,去生成内部的独立表单系统,以做校验
该方案也存在一些缺陷,
- 校验错误的样式,每个组件都需要手动写css,太不方便了,这里修改为底层自动适配,使用者无需关心。
 - useFormGroup改为了ixs的
 - 提示语位置按照我们控制器视觉标准进行修改
 
# 使用
控制器中已添加对应的使用说明,这里简单给个示例:
vue组件
<template>
  <IxsForm ref="formRef" :control="formGroupControl">
    <IxsFormItem :name="controlName" label="">
      <IxsSelect
        :data-source="ALARM_PUSH_METHOD"
        :placeholder="$i('global.select.dropdown')"
        :multiple="true"
        :control="controlName"
      />
    </IxsFormItem>
  </IxsForm>
</template>
<script lang="ts" setup>
import { useVueToExtField } from "@/utils/vueToExt";
import XX from "./Index.vue";
const props = withDefaults(
  defineProps<{
    allowBlank?: boolean;
    min?: number;
    max?: number;
    validators?: ValidatorFn[] | ValidatorFn;
  }>(),
  {
    allowBlank: true,
    min: 0,
    max: 0,
    validators: () => [],
  }
);
const emits = defineEmits<{
  (e: "change", value: DeviceItem[]): void;
}>();
const {
  formGroupControl,
  controlName,
  getValue,
  setValue,
  isValid,
  validate,
  clearInvalid,
  getInvalid,
} = useVueToExtField<DeviceItem[]>([], props, emits);
defineExpose({
  // 必须暴露的方法
  getValue,
  setValue,
  // 暴露校验相关的方法
  isValid,
  validate,
  clearInvalid,
  getInvalid,
});
</script>
生成ext组件
import { createVue2ExtField, createExtComponent } from '@/utils/vueToExt';
import DeviceSelectFormItem from '@/components/business/DeviceSelect/FormItem.vue';
const ExtComponent = createExtComponent(DeviceSelectFormItem, 'device_select');
const vue2ExtField = createVue2ExtField(ExtComponent);
const DeviceSelectField = Ext.extend(vue2ExtField, {
  // 重写设置vue组件的方法
  setPropsData() {
    const { allowBlank, min, max, validators, emptyText } = this;
    this.setData({
      allowBlank,
      min,
      max,
      validators,
      placeholder: emptyText,
      defaultWidth: this.getDefaultWidth(),
    });
  },
  // 重写当校验失败时qtip需要加到哪些dom上
  getNeedAddQtipDom() {
    return [
      document.querySelector(`.${this.getCls()} .ix-selector-input-inner`),
      document.querySelector(`.${this.getCls()} .ix-selector-overflow`),
    ].filter(Boolean);
  },
});
export default DeviceSelectField;
然后将生成的ext组件像往常普通的ext组件一样,实例化调用即可。
# 总结
通过以上方案:
- ext的已有的表单组件(交换机面板、各种设备选择器等)可以快速接入vue中,不再需要重写。
 - 原ext页面中做改动,可以使用vue编写,降低开发难度,提高开发效果。